package org.codingq92.project.nio_chat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO 聊天室 Server
 *
 * @author: CodingQ92
 * @date: 2025/6/30 23:50
 */
public class NIOChatServer {

    private static final int port = 18000;
    private static Selector selector;
    private static ServerSocketChannel ssChannel;

    public static void main(String[] args) {
        try {
            // 1. 创建选择器
            selector = Selector.open();
            // 2. 创建服务器通道
            ssChannel = ServerSocketChannel.open();
            // 3. 绑定服务端口
            ssChannel.bind(new InetSocketAddress(port));
            ssChannel.configureBlocking(false);
            System.out.println("NIO 启动，监听端口：" + port);
            // 4. 将通道注册到选择器，并监听连接事件
            ssChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (selector.select() > 0) {
                // 遍历所有的就绪事件 包含所有注册通道：实际上它仅包含就绪的通道，可能为空
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    if (sk.isAcceptable()) { // 处理【连接事件】：有新客户端连接
                        handleAccept();
                    } else if (sk.isReadable()) { // 处理【读事件】：客户端有数据发送
                        handleRead(sk);
                    } else if (sk.isWritable()) { // 处理【写事件】：通道可写数据（可选，按需实现）
                        handleWrite(sk);
                    }
                }
                // 处理完毕移除当前事件
                iterator.remove();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void handleAccept() throws IOException {
        // 接受客户端连接（非阻塞，无连接时返回 null，但此处已触发 OP_ACCEPT，故一定有连接）
        SocketChannel clientChannel = ssChannel.accept();
        clientChannel.configureBlocking(false); // 客户端通道也设为非阻塞
        // 将客户端通道注册到选择器，监听【读事件】（后续可扩展写事件）
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("新用户加入：" + clientChannel.getRemoteAddress());
    }

    private static void handleRead(SelectionKey sk) {
        SocketChannel clientChannel = null;
        try {
            clientChannel = (SocketChannel) sk.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int rLen = clientChannel.read(buffer);
            if (rLen > 0) {
                buffer.flip(); // 翻转缓冲区
                String message = new String(buffer.array(), 0, buffer.remaining());
                System.out.println("收到客户端消息：" + message);
                // 开始分发消息到每个链接在线的客户端上
                sendMsgToAllClient(clientChannel, message);
            } else if (rLen == -1) {
                System.out.println(clientChannel.getRemoteAddress() + "下线了!");
                sk.cancel(); // 取消注册
                clientChannel.close();
            }
        } catch (Exception e) {
            try {
                System.out.println(clientChannel.getRemoteAddress() + "下线了!");
                sk.cancel(); // 取消注册
                clientChannel.close();
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    private static void sendMsgToAllClient(SocketChannel clientChannel, String msg) throws IOException {
        System.out.println("开始转发消息...");
        // 所有已注册到该 Selector 的通道集合（即所有 SelectionKey）。
        Set<SelectionKey> keys = selector.keys();
        for (SelectionKey key : keys) {
            Channel targetChannel = key.channel();
            // 筛选出非自己的SocketChannel
            if (targetChannel instanceof SocketChannel && targetChannel != clientChannel) {
                // 获取该通道的缓冲区并发送消息
                ByteBuffer allocate = ByteBuffer.wrap(msg.getBytes());
                ((SocketChannel) targetChannel).write(allocate);
            }
        }
    }

    private static void handleWrite(SelectionKey sk) throws IOException {
        SocketChannel clientChannel = (SocketChannel) sk.channel();
        // 假设已准备好要写的数据
        ByteBuffer buffer = ByteBuffer.wrap("服务端回复".getBytes());
        clientChannel.write(buffer);
        sk.interestOps(SelectionKey.OP_READ); // 写完后重新监听读事件
    }
}
